namespace TomatoBreak.UnitSpecs.DomainObjects
{
    using System;
    using System.Security;

    using Machine.Specifications;

    using Infrastructure;
    using TomatoBreak.DomainObjects;

    public abstract class UserSpec
    {
        protected const string UserName = "KaziManzurRashid";
        protected const string Password = "secret";
        protected const string Email = "kazimanzurrashid@gmail.com";
    }

    [Subject(typeof(User))]
    public class when_creating_user_with_null_or_blank_user_name : UserSpec
    {
        static Exception exception;

        Because of = () => exception = Catch.Exception(() => new User(string.Empty, Password, Email));

        It should_throw_exception = () => exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(User))]
    public class when_creating_user_with_null_or_blank_password : UserSpec
    {
        static Exception exception;

        Because of = () => exception = Catch.Exception(() => new User(UserName, string.Empty, Email));

        It should_throw_exception = () => exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(User))]
    public class when_creating_user_with_null_or_blank_email : UserSpec
    {
        static Exception exception;

        Because of = () => exception = Catch.Exception(() => new User(UserName, Password, null));

        It should_throw_exception = () => exception.ShouldBeOfType<ArgumentNullException>();
    }

    public abstract class CreatedUserSpec : UserSpec
    {
        protected static User user;

        Establish context = () => user = new User(UserName, Password, Email);
    }

    [Subject(typeof(User))]
    public class when_user_is_created : CreatedUserSpec
    {
        It should_have_same_user_name_which_is_passed_in_constructor = () => user.UserName.ShouldEqual(UserName);

        It should_have_hashed_password = () =>
        {
            user.Password.ShouldNotBeNull();
            user.Password.ShouldNotBeEmpty();
            user.Password.ShouldNotEqual(Password);
        };

        It should_auto_generate_salt = () =>
        {
            user.Salt.ShouldNotBeNull();
            user.Salt.ShouldNotBeEmpty();
        };

        It should_have_same_email_which_is_passed_in_constructor = () => user.Email.ShouldEqual(Email);

        It should_have_empty_tasks = () => user.Tasks.ShouldBeEmpty();
    }

    [Subject(typeof(User))]
    public class when_checking_password_matching_with_null_or_blank_password : CreatedUserSpec
    {
        static Exception exception;

        Because of = () => exception = Catch.Exception(() => user.HasPassword(null));

        It should_throw_exception = () => exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(User))]
    public class when_checking_password_matching_with_incorrect_password : CreatedUserSpec
    {
        static bool matched;

        Because of = () => matched = user.HasPassword("foobar");

        It should_not_match = () => matched.ShouldBeFalse();
    }

    [Subject(typeof(User))]
    public class when_checking_password_matching_with_correct_password : CreatedUserSpec
    {
        static bool matched;

        Because of = () => matched = user.HasPassword(Password);

        It should_match = () => matched.ShouldBeTrue();
    }

    [Subject(typeof(User))]
    public class when_changing_password_with_null_or_blank_new_password : CreatedUserSpec
    {
        static Exception exception;

        Because of = () => exception = Catch.Exception(() => user.ChangePassword(Password, null));

        It should_throw_exception = () => exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(User))]
    public class when_changing_password_with_incorrect_old_password : CreatedUserSpec
    {
        static Exception exception;

        Because of = () => exception = Catch.Exception(() => user.ChangePassword("foobar", "barbaz"));

        It should_throw_exception = () => exception.ShouldBeOfType<SecurityException>();
    }

    [Subject(typeof(User))]
    public class when_password_is_changed : CreatedUserSpec
    {
        static string oldPassword;

        Establish context = () => oldPassword = user.Password;

        Because of = () => user.ChangePassword(Password, "foobarbaz");

        It should_update_password_property = () => user.Password.ShouldNotEqual(oldPassword);
    }

    [Subject(typeof(User))]
    public class when_user_is_locked : CreatedUserSpec
    {
        Because of = () => user.Lock();

        It should_return_true_for_locked = () => user.Locked.ShouldBeTrue();
    }

    [Subject(typeof(User))]
    public class when_user_is_unlocked : CreatedUserSpec
    {
        Because of = () =>
        {
            user.Lock();
            user.Unlock();
        };

        It should_return_false_for_locked = () => user.Locked.ShouldBeFalse();
    }

    [Subject(typeof(User))]
    public class When_task_is_added : CreatedUserSpec
    {
        static Task task;

        Because of = () => task = user.AddTask("Dummy", Clock.UtcNow());

        It should_exist_in_tasks = () => user.Tasks.ShouldContain(task);
    }
}